﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Data.Objects;
using GlobalObjects;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Data.Entity.Infrastructure;
using WCFServiceWebRole1.DataObjects;
using System.ServiceModel.Web;
using System.Net;
using HaversineFormula;

namespace DataAccessLayer
{
    public class SensorEntity
    {
        /// <summary>
        /// returns the limits for the specified sensor
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <returns>Limits object</returns>
        public Limits getLimits_DAL(int sensorId) {
            Limits limit = new Limits();
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (query.Count() > 0)
                {
                    limit.upper_limit = query.First().upper_limit;
                    limit.lower_limit = query.First().lower_limit;
                    limit.trigger_name = query.First().trigger_name;
                }
                else {
                    DbUpdateException exc = new DbUpdateException("no sensor was found");
                    throw exc;
                }
                return limit;
            }
        }
        /// <summary>
        /// adds or updates the trigger information of a sensor
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <param name="upper">upper limit</param>
        /// <param name="lower">lower limit</param>
        /// <param name="trigger">name of the key which is sent from the sensor</param>
        public void addOrUpdateLimits_DAL(int sensorId, Nullable<Double> upper, Nullable<Double> lower, string trigger)
        {
            Boolean isCorrect = true;
                using (var context = new MSR2DBEntities())
                {
                    var query = from s in context.sensors
                                where s.id == sensorId
                                select s;
                    if(query.Count()>0){
                        if(upper != null){
                        query.First().upper_limit = upper;
                        }
                        if (lower != null)
                        {
                            query.First().lower_limit = lower;
                        }
                        if(trigger != null){
                            query.First().trigger_name = trigger;
                        }
                        if (trigger == null && query.First().trigger_name == null)
                        {
                            isCorrect = false;
                        }

                    }
                    if (isCorrect)
                    {
                        context.SaveChanges();
                    }
                    else {
                        DbUpdateException exc = new DbUpdateException("No trigger name is set");
                        throw exc;
                    }
                }
        }

        /// <summary>
        /// returns a list of available and accessible sensors to a sensor (including distance to each)
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <returns>a list of SensorDistance</returns>
        public List<SensorDistance> getNearestSensors_DAL(int sensorId)
        {
            List<SensorDistance> list = new List<SensorDistance>();
            List<SensorDistance> finalList = new List<SensorDistance>();
            string type;
            int sensorDeviceId;
            int userId;
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (query.Count() == 1)
                {
                    type = query.First().type;
                    sensorDeviceId = query.First().device_id.Value;

                    var userIdQuery = from d in context.devices
                            where d.id == sensorDeviceId
                            select d;

                    userId = userIdQuery.First().user_id.Value;

                    var typeQuery = from s in context.sensors
                                    where s.type == type && s.id != sensorId
                                    select s;
                    foreach (var sen in typeQuery)
                    {
                        if (isAuthorised_DAL(sen.id, userId))
                        {
                            int deviceId2 = sen.device_id.Value;
                            double distance = getDistance_DAL(sensorDeviceId, deviceId2);
                            SensorDistance sd = new SensorDistance(sen.id, distance);
                            list.Add(sd);
                            if (sd.distance == 0 && sd.sensorId != sensorId)
                            {
                                List<SensorDistance> SortedList = list.OrderBy(o => o.distance).ToList();
                                foreach (var item in SortedList)
                                {
                                    finalList.Add(item);
                                }
                                return finalList;
                            }
                        }
                    }
                }
                else {
                    InvalidOperationException exc = new InvalidOperationException("error in getNearestSensors 1");
                    throw exc;
                }
                if(list.Count!=0){
                   List<SensorDistance> SortedList = list.OrderBy(o => o.distance).ToList();
                    foreach(var item in SortedList){
                            finalList.Add(item);
                      }
                   return finalList;
                }
                else {
                    return finalList;
                }
            }
        }
        /// <summary>
        /// Given two device ids, returns the distance between in Km 
        /// </summary>
        /// <param name="sensor1DeviceId">device id for sensor-1</param>
        /// <param name="sensor2DeviceId">device id for sensor-2</param>
        /// <returns>distance in KM</returns>
        public double getDistance_DAL(int sensor1DeviceId, int sensor2DeviceId) {
            //get lat-long
            Boolean success1 = false;
            Boolean success2 = false;
            double lat1=0;
            double lat2=0;
            double long1=0;
            double long2=0;
            double distance=0;
            using (var context = new MSR2DBEntities())
            {
                var deviceQuery1 = from d in context.devices
                                   where d.id == sensor1DeviceId
                                  select d;
                if(deviceQuery1.Count()!=0){
                    success1 = true;
                    string[] latlong1 = deviceQuery1.First().longlat.Split(',');
                    lat1 = Convert.ToDouble(latlong1[0]);
                    long1 = Convert.ToDouble(latlong1[1]);
                }
                var deviceQuery2 = from d in context.devices
                                   where d.id == sensor2DeviceId
                                   select d;
                if (deviceQuery2.Count() != 0)
                {
                    success2 = true;
                    string[] latlong1 = deviceQuery2.First().longlat.Split(',');
                     lat2 = Convert.ToDouble(latlong1[0]);
                     long2 = Convert.ToDouble(latlong1[1]);
                }
                if (!success1 || !success2)
                {
                    InvalidOperationException exc = new InvalidOperationException("invalid coordinates");
                    throw exc;
                }                
            }
            if (success1 && success2)
            {
            //Position1
            Position pos1 = new Position();
            pos1.Latitude = lat1;
            pos1.Longitude = long1;
            //Position2
            Position pos2 = new Position();
            pos2.Latitude = lat2;
            pos2.Longitude = long2;

            Haversine hv = new Haversine();
            distance = hv.Distance(pos1, pos2, DistanceType.Kilometers);
            }
            return distance;
        }

        /// <summary>
        /// returns the status of the given sensor
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <returns>sensor status</returns>
        public Boolean getSensorStatus_DAL(int sensorId) {
            Boolean status = false;
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (query.Count() == 1)
                {
                    var sensorX = query.First();
                    status = sensorX.status.Value;
                }
                else {
                    InvalidOperationException exc = new InvalidOperationException("invalid sensor id");
                    throw exc;
                }
            }
            return status;
        }
        /// <summary>
        /// sets the status of the given sensor to the new one
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <param name="newStatus">sensor status</param>
        public void setSensorStatus_DAL(int sensorId,Boolean newStatus) { 
             using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (query.Count() == 1)
                {
                    var sensorX = query.First();
                    sensorX.status = newStatus;
                    context.SaveChanges();
                }
                else {
                    InvalidOperationException exc = new InvalidOperationException("invalid sensor id");
                    throw exc;
                }
            }
        
        }
        /// <summary>
        /// checks whether the given userId is authorised to access the given sensor or not.
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <param name="userId">user id</param>
        /// <returns>true if the user is authorised, false otherwise.</returns>
        public Boolean isAuthorised_DAL(int sensorId,int userId) {
            Boolean authorised = false;
            Boolean deviceIdExist = false;
            Boolean isOwner = false;
            Boolean isPublic = false;
            Boolean isShared = false;
            using (var context = new MSR2DBEntities())
            {
                var sensorQuery = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (sensorQuery.Count() != 0)
                {
                    int deviceId = sensorQuery.First().device_id.Value;
                    var deviceQuery = from d in context.devices
                                      where d.id == deviceId
                                      select d;
                    if (deviceQuery.Count() != 0)
                    {
                        var devicex = deviceQuery.First();
                        deviceIdExist = true;
                        if (deviceIdExist)
                        {
                            if (devicex.user_id == userId)
                            {
                                isOwner = true;
                            }
                            if (devicex.privacy == "public")
                            {
                                isPublic = true;
                            }
                        }
                        var sharedquery = from d in context.devices
                                          join s in context.shareds on d.id equals s.device_id
                                          where s.user_id == userId && deviceId == s.device_id
                                          select d;
                        int count = sharedquery.Count();
                        if (sharedquery.Count()!=0)
                        {
                            isShared = true;
                       }

                        if (isOwner || isPublic || isShared)
                        {
                            authorised = true;
                        }
                    }
                    else
                    {
                        InvalidOperationException exc = new InvalidOperationException("device id cannot be found");
                        throw exc;
                    }
                }
                else
                {
                    InvalidOperationException exc = new InvalidOperationException("sensor id cannot be found");
                    throw exc;
                }
            }
            return authorised;
        }

        /// <summary>
        /// checks if the given name already exist in the database or not
        /// </summary>
        /// <param name="sensorName">sensor name</param>
        /// <returns>true if exist, false otherwise</returns>
        public Boolean sensorNameExist_DAL(string sensorName){
            Boolean exist = false;
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.name == sensorName
                            select s;
                if (query.Count() != 0)
                {
                    exist = true;
                }
            }
            return exist;
        } 

        /// <summary>
        /// updates the information of the given Sensor
        /// </summary>
        /// <param name="modifiedSensor">contains the new details</param>
        /// <returns>returns the updated Sensor</returns>
        public Sensor updateSensorDetails_DAL(Sensor modifiedSensor) {
            Sensor updatedSensor = new Sensor();
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == modifiedSensor.Id
                            select s;
                if (query.Count() == 0)
                {
                    InvalidOperationException exc = new InvalidOperationException();
                    throw exc;
                }
                else {
                    var sensorX = query.First();
                    updatedSensor.Id = modifiedSensor.Id;
                    //
                    if (modifiedSensor.Deviceid != 0)
                    {
                        try
                        {
                            sensorX.device_id = modifiedSensor.Deviceid;
                            updatedSensor.Deviceid = modifiedSensor.Deviceid;
                            context.SaveChanges();
                        }
                        catch (System.Data.Entity.Infrastructure.DbUpdateException e)
                        {
                            throw e;
                        }
                    }
                        else {
                           updatedSensor.Deviceid = sensorX.device_id.Value;
                        }

                    if (modifiedSensor.Name != null)
                    {
                        sensorX.name = modifiedSensor.Name;
                        updatedSensor.Name = modifiedSensor.Name;
                    }
                        else {
                            updatedSensor.Name = sensorX.name;
                        }

                    if (modifiedSensor.Type != null)
                    {
                        sensorX.type = modifiedSensor.Type;
                        updatedSensor.Type = modifiedSensor.Type;
                    }
                        else {
                            updatedSensor.Type = sensorX.type;
                        }

                    if (modifiedSensor.Make != null)
                    {
                        sensorX.make = modifiedSensor.Make;
                        updatedSensor.Make = modifiedSensor.Make;
                    }
                        else {
                            updatedSensor.Make = sensorX.make;
                        }

                    if (modifiedSensor.Interval_measure != null)
                    {
                        sensorX.interval_measure = modifiedSensor.Interval_measure;
                        updatedSensor.Interval_measure = modifiedSensor.Interval_measure;
                    }
                        else {
                            updatedSensor.Interval_measure = sensorX.interval_measure;
                        }

                    if (modifiedSensor.Interval_send != null)
                    {
                        sensorX.interval_send = modifiedSensor.Interval_send;
                        updatedSensor.Interval_send = modifiedSensor.Interval_send;
                    }
                        else {
                            updatedSensor.Interval_send = sensorX.interval_send;
                        }

                    if (modifiedSensor.Metadata != null)
                    {
                        sensorX.metadata = modifiedSensor.Metadata;
                        updatedSensor.Metadata = modifiedSensor.Metadata;
                    }
                        else {
                            updatedSensor.Metadata = sensorX.metadata;
                        }
                    context.SaveChanges();
                }
            }
            return updatedSensor;
        }

        /// <summary>
        /// returns a Sensor object based on the given sensorId
        /// </summary>
        /// <param name="sensorId">id of the required sensor</param>
        /// <returns>Sensor object</returns>
        public Sensor getSensorById_DAL(int sensorId) {
            Sensor foundSensor = new Sensor();
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (query.Count() == 0)
                {
                    InvalidOperationException exc = new InvalidOperationException("sensor id does not exist");
                    throw exc;
                }
                else
                {
                    var x = query.First();
                    foundSensor.Id = x.id;
                    foundSensor.Deviceid = x.device_id.Value;
                    foundSensor.Name = x.name;
                    foundSensor.Type = x.type;
                    foundSensor.Make = x.make;
                    foundSensor.Interval_measure = x.interval_measure;
                    foundSensor.Interval_send = x.interval_send;
                    foundSensor.Metadata = x.metadata;
                    foundSensor.Status = x.status.Value;
                }
            }
            return foundSensor;
        }

        /// <summary>
        /// returns the type of the specified sensor 
        /// </summary>
        /// <param name="sensorId">id of the sensor</param>
        /// <returns>type of the sensor</returns>
        public string getTypeById_DAL(int sensorId) {
            string type = ""; 
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.id == sensorId
                            select s;
                if (query.Count() == 0)
                {
                    InvalidOperationException exc = new InvalidOperationException();
                    throw exc;
                }
                else {
                    type = query.First().type;
                }
            }
            return type;
        }

        /// <summary>
        /// returns a list of sensors with the matching deviceid as specified in the parameter
        /// </summary>
        /// <param name="deviceId"></param>
        /// <returns>list of sensors</returns>
        public List<Sensor> getSensorsByDeviceId_DAL(int deviceId)
        {
            List<Sensor> list = new List<Sensor>();
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.device_id == deviceId 
                            select s;
                foreach (var x in query)
                {
                    Sensor foundSensor = new Sensor();
                    foundSensor.Id = x.id;
                    foundSensor.Deviceid = x.device_id.Value;
                    foundSensor.Name = x.name;
                    foundSensor.Type = x.type;
                    foundSensor.Make = x.make;
                    foundSensor.Interval_measure = x.interval_measure;
                    foundSensor.Interval_send = x.interval_send;
                    foundSensor.Metadata = x.metadata;
                    foundSensor.Status = x.status.Value;
                    list.Add(foundSensor);
                }
            }
            return list;
        }

        public Boolean sensorNameExistGivenDeviceId_DAL(string sensorName, int deviceId)
        {
            Boolean exist = false;
            using (var context = new MSR2DBEntities())
            {
                var query = from s in context.sensors
                            where s.name == sensorName && s.device_id == deviceId 
                            select s;
                if (query.Count() != 0)
                {
                    exist = true;
                }
            }
            return exist;
        } 
            
        /// <summary>
        /// Adds a new sensor to the sensors table, given the name is unique for that device
        /// </summary>
        /// <param name="newSensor">Sensor that will be added</param>
        /// <returns>returns the new added sensor with unique id assigned to it</returns>
        public Sensor addSensors_DAL(Sensor newSensor)
        {
            Sensor createdSensor = new Sensor();
            using (var context = new MSR2DBEntities())
            {
                if (!sensorNameExistGivenDeviceId_DAL(newSensor.Name, newSensor.Deviceid))
                {
                    var nsensor = new sensor()
                    {
                        device_id = newSensor.Deviceid,
                        name = newSensor.Name,
                        type = newSensor.Type,
                        make = newSensor.Make,
                        interval_measure = newSensor.Interval_measure,
                        interval_send = newSensor.Interval_send,
                        metadata = newSensor.Metadata,
                        status = false
                    };
                    context.sensors.Add(nsensor);
                    context.SaveChanges();

                    createdSensor.Id = nsensor.id;
                    createdSensor.Deviceid = nsensor.device_id.Value;
                    createdSensor.Name = nsensor.name;
                    createdSensor.Type = nsensor.type;
                    createdSensor.Make = nsensor.make;
                    createdSensor.Interval_measure = nsensor.interval_measure;
                    createdSensor.Interval_send = nsensor.interval_send;
                    createdSensor.Metadata = nsensor.metadata;
                    Console.WriteLine("New sensor is added successfully- ID is:" + nsensor.id);
                    return createdSensor;
                }
                else {
                    InvalidOperationException exc = new InvalidOperationException("given name already exist for this device");
                    throw exc;
                }
            }
        }
    }
}
